/**
  ETFRoot project, Anyang Normal University && IMP-CAS
  \class ETFUserVMEUnpacker
  \brief unpacker for raw data collected by VME daq
  NOTE that this is devised to be an abstract base class, since VME decoding
  implementation depends on the plugins, thus should be specially written for
  different VME plugin configurations. It is the user's responsibility to derive
  this class to implement for their own experiments.
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2021-09-27
  \date 2021-10-02 last modified
  \attention
  changelog
  <table>
  <tr>  <th>Date         <th>Author      <th>Description                   </tr>
  <tr>  <td>2021-09-27   <td>Asia Sun    <td>file created                  </tr>
  <tr>  <td>2021-10-01   <td>Asia Sun    <td>completing                    </tr>
  </table>

  \copyright Copyright (c) 2021 Anyang Normal U. && IMP-CAS with LGPLv3 LICENSE
*/

#include <map>
#include <TClonesArray.h>
#include "FairRootManager.h"

#include "vme_event_head.h"
#include "ETFUserVMERawEvent.h"
#include "ETFVMEEvent.h"
#include "ETFUserVMEUnpacker.h"
#include "ETFHPTDCChannel.h"
#include "ETFAmplitudeChannel.h"
#include "ETFMsg.h"

using std::map;

ClassImp(ETFUserVMEUnpacker);

ETFUserVMEUnpacker::ETFUserVMEUnpacker() : ETFVMEUnpacker()
  ,fRawEvent(new ETFUserVMERawEvent()), fDebug(false){}

ETFUserVMEUnpacker::~ETFUserVMEUnpacker(){
  if(fRawEvent){ delete fRawEvent; fRawEvent = nullptr; }
} // end the dtor

void ETFUserVMEUnpacker::Register(){
  ETFVMEUnpacker::Register();

  FairRootManager *man = FairRootManager::Instance();
  if(man){
    // branchname, foldername, array address, persistent or transient
    man->Register("VMERawEvent", "VME", fRawEvent, true);
  } // end if
} // end member function Register

void ETFUserVMEUnpacker::Reset(){
  ETFVMEUnpacker::Reset();
  fRawEvent->Reset();
} // end member function Reset

// customizable user event-decoding for a certain vme-config
// user to implement this method should be aware that the data to be decoded
// starts from fData[fPos+3] to fData[fPos+fEvent->fEventHead.event_length]
void ETFUserVMEUnpacker::UserDecode(){
  static map<int, ETFHPTDCChannel *> tchMap; // <chId, tch*>: for assigning HPTDC edges
  tchMap.clear();

  vme_event_head &ev_h = fEvent->GetEventHead();
  int ev_len = ev_h.event_length;
  TClonesArray *tchs = fEvent->GetHPTDCChannels();
  TClonesArray *achs = fEvent->GetAmplitudeChannels();


  int id_v1190 = 0, id_v830 = 0; // the No. of v1190 and v830 plugin
  int nsca = 0, chId = -1;
  // scalers for counting purposes //
  auto &psca = fEvent->GetPSca(), &sca = fEvent->GetSca();
  auto &dsca = fEvent->GetDSca();
  for(int i = 3; i < ev_len; i++){ // loop over channels in an event
    // cout << "i: " << i << endl;
    int chData = fData[fPos+i]; // channel data
    int slot = chData>>27 & 0x1F; // [27,31]: slot number

    switch(slot){
      case 5: case 17:
        if(!id_v830 && !id_v1190){ // QDC or ADC
          int amplitude = -1; ETFAmplitudeChannel *ach = nullptr;
          switch((chData>>24) & 0x7){ // header: [24-26]
            case 2: // data header
              if(fDebug){
                cout << "Header for v7x5, slot: " << slot;
                cout << ", count: " << (chData>>8 & 0x3F);
                getchar();
              } // end if(debug)
              break;
            case 4: // data trailer
              if(fDebug){
                cout << "Trailer for v7x5, slot: " << slot << endl; // DEBUG
                getchar();
              } // end if(debug)
              break;
            case 0: // data channel
              chId = chData >> 16 & 0x1F; // [16-21]: channel Id
              amplitude = chData & 0xFFF; // [0-11]: channel data
              ach = dynamic_cast<ETFAmplitudeChannel *>(achs->ConstructedAt(achs->GetEntries()));
              ach->Reset(); ach->SetIsPXI(false);
              switch(slot){
                case 5: // QDC v965
                  // offset the channel id to merge into PXI channel id system
                  ach->SetChannelId(chId + 8401);
                  fRawEvent->qdc(0, chId) = amplitude;
                  if(chId >= 32)
                    ETFMsg::Error("ETFUserVMEUnpacker",
                      "UserDecode: chId out of range for qdc plugin: %d", chId);
                  break;
                case 17: // ADC v785
                  ach->SetChannelId(chId + 8501);
                  fRawEvent->adc(chId) = amplitude;
                  if(chId >= 32)
                    ETFMsg::Error("ETFUserVMEUnpacker",
                      "UserDecode: chId out of range for adc plugin: %d", chId);
                  break;
                default:
                  ETFMsg::Error("ETFUserVMEUnpacker",
                    "UserDecode: abnormal slot number for non-mTDC plugins: slot: %d", slot);
                  break;
              } // end inner switch(slot)
              ach->AddAmplitude(amplitude);
              break;
            default: break;
          } // end switch(header)
          continue;
        } // end if(QDC or ADC)
        break;
      case 8: case 16:
        if(!id_v830){ // HPTDC global header or trailer
          int geo = chData & 0x1F; // [0-4]: plugin id (slot No. in practical)
          // ------------------ HPTDC GLOBAL HEADER ---------------------------- //
          if(8 == slot && (9 == geo || 11 == geo)){ // HPTDC global header
            id_v1190 = geo;
            if(fDebug){
              cout << "HPTDC Global Header for v1190, geo: " << geo;
              cout << ", id_v1190: " << id_v1190 << endl;
              getchar();
            } // end if(debug)
          } // end if(HPTDC group header)
          // ----------------- HPTDC GLOBAL TRAILER ---------------------------- //
          if(16 == slot && (9 == geo || 11 == geo)){ // HPTDC global trailer
            if(fDebug){
              cout << "HPTDC Global Trailer for v1190, geo: " << geo;
              cout << ", id_v1190: " << id_v1190 << endl;
              getchar();
            } // end if(debug)
            if(9 != id_v1190 && 11 != id_v1190){ // HPTDC global trailer
              cout << "DEBUG slot: " << slot << endl;
              cout << "DEBUG HPTDC Global Trailer for v1190, geo: " << geo;
              cout << ", id_v1190: " << id_v1190 << endl;
              ETFMsg::Error("ETFUserVMEUnpacker",
                "UserDecode: wrong id_v1190 in HPTDC trailer: %d", id_v1190);
            } // end if(HPTDC global trailer)
            id_v1190 = 0;
          } // end if(HPTDC global trailer)
          continue;
        } // end if(HPTDC global header or trailer)
        break;
      case 21:
        if(!id_v1190){ // scaler v830 header
          id_v830++;
          nsca = 0;
          if(fDebug){
            cout << "v830 header encountered" << endl;
            getchar();
          } // end if(debug)
          continue; // skip the rest of the loop to next data channel
        } // end if(v830 header)
        break;
      case 1:
        if(id_v1190){ // TDC group header
          fEvent->SetBunchID(chData & 0xFFF);
          fEvent->SetEventID(chData>>12 & 0xFFF);
          if(fDebug){
            cout << "HPTDC group header for v1190, id_v1190: " << id_v1190 << endl;
            cout << "bunchID: " << (chData & 0xFFF) << "\teventID: " << (chData>>12 & 0xFFF) << endl;
            cout << "TDCId: " << (chData>>24 & 0xF) << "\theadMark: " << (chData>>28 & 0xF) << endl;
            getchar();
          } // end if(debug)
          continue;
        } // end if TDC group header
        break;
      case 3:
        if(id_v1190 && !id_v830){ // TDC group trailer
          if(fDebug){
            int cnt = (chData & 0xFFF) - 2; // exclude the word count of header and trailer
            cout << "TDC group trailer for v1190, id_v1190: " << id_v1190 << endl;
            cout << cnt << " hits." << endl; // DEBUG
            int wordCnt = chData & 0xFFF, eventId = chData>>12 & 0xFFF;
            int TDCId = chData>>24 & 0xF, headMark = chData>>28 & 0xF;
            cout << "wordCnt: " << wordCnt << "\teventId: " << eventId << endl;
            cout << "TDCId: " << TDCId << "\theadMark: " << headMark << endl;
            getchar(); // DEBUG
          } // end if(debug)
          continue;
        } // end if TDC group trailer
        break;
      case 0:
        if(id_v1190 && !id_v830){ // HPTDC physical data zone
          chId = chData>>19 & 0x7F; // local chId in the plugin
          if(9 == id_v1190) chId += 8001;
          else if(11 == id_v1190) chId += 8201;
          if(!tchMap.count(chId)){
            tchMap[chId] = dynamic_cast<ETFHPTDCChannel *>(tchs->ConstructedAt(tchs->GetEntries()));
            tchMap[chId]->Reset(); tchMap[chId]->SetChannelId(chId); tchMap[chId]->SetIsPXI(false);
          } // end if
          if(chData>>26 & 0x1) tchMap[chId]->AddTrailingEdgeH(chData & 0x7FFFF);
          else tchMap[chId]->AddLeadingEdgeH(chData & 0x7FFFF);
          continue;
        } // end if(id_v1190 && !id_v830)
        break;
      default: break;
    } // end switch(slot)

    if(id_v830){ // scaler v830 data zone
      psca[nsca] = sca[nsca];
      sca[nsca] = chData;
      dsca[nsca] = sca[nsca] - psca[nsca];
      nsca++;
      if(16 == nsca){
        id_v830 = 0; // all 16 scaler channels have been read
        if(fDebug){
          cout << "index: " << ev_h.index << endl; // DEBUG
          for(int j = 0; j < 16; j++){ // DEBUG
            cout << "sca[" << j << "]: " << sca[j]; // DEBUG
            cout << "\tpsca[" << j << "]: " << psca[j]; // DEBUG
            cout << "\tdsca[" << j << "]: " << dsca[j] << endl; // DEBUG
          } // end for over i
          getchar(); // DEBUG
        } // end if(debug)
      } // end if(16 == nsca)
    } // end if(id_v830)
  } // end for over channels in an event

  if(fDebug){
    fEvent->Print();
    fRawEvent->Print();
  } // end if(fDebug)
} // end member function UserDecode
